package cmp.compile;

import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.sql.ResultSet;
import java.util.Vector;

import util.Debug.ErrorReport;
import util.methods.HostsCfg;
import util.methods.TimeHelpers;
import cmp.main.Globals;
import cmp.viewer.ViewerFrame;
import cmp.compile.panels.CompilePanel;
import cmp.compile.panels.MethodBestRecipPanel;
import cmp.compile.panels.MethodOrthoMCLPanel;
import cmp.compile.panels.MethodTransitivePanel;
import cmp.compile.panels.MethodUserDefPanel;
import cmp.compile.Summary;
import cmp.compile.MethodUserDef;
import cmp.compile.MethodOrthoMCL;
import cmp.compile.MethodBestRecip;
import cmp.compile.MethodTransitive;
import assem.DBConn;
import cmp.database.DBinfo;
import cmp.database.Schema;

/** 
 * Starts up the Panel
 */
public class CompileMain {
	static public CompileMain cmpMain;
	public static String strGODB = "";
	
	public static void main(String[] args) {
		try {
			ErrorReport.setErrorReportFileName(Globals.CmpErrorLog);
			CompileMain.run(args);
		} catch(Exception e) {
			ErrorReport.reportError(e);
		}
	}
	
	/**************************************************
	 * Select PAVE databases from interface
	 */
	public static void run(String [] args) throws Exception {
		cmpMain = new CompileMain();	
		hosts = new HostsCfg();

	}
	
	//Creates Compile Frame 
	public CompileMain() 
	{ 
		theFrame = new ViewerFrame(this);
		theFrame.addWindowListener(new WindowListener() {

			public void windowActivated(WindowEvent arg0) {}
			public void windowClosed(WindowEvent arg0) {System.exit(0);}
			public void windowClosing(WindowEvent arg0) {}
			public void windowDeactivated(WindowEvent arg0) {}
			public void windowDeiconified(WindowEvent arg0) {}
			public void windowIconified(WindowEvent arg0) {}
			public void windowOpened(WindowEvent arg0) {}
		});
		cmpPanel = theFrame.getCompilePanel();

		theFrame.pack();
		theFrame.setVisible(true);			
	}
	/***************************************************************
	 * Create Database
	 * Called by species panel when user clicks to create database
	 ***************************************************************/
	public void createDatabase() {
		try {
			PrtDateMsg("\nStart Create Database");		
			
			if (!checkAAfiles()) return;
			logFileObj = createLogFile();
			
			cmpPanel.allowExecute(false);	
	   		long startTime = CompileMain.getTime();
	   		
	   	// Create schema and enter Info table
			if (!validateCmpPAVEdb()) return;
	   	
	   	// Assembly, hits and unitrans tables
			new LoadPAVE(mDB, cmpPanel);
			
		// Make combined file for blast
			generateFastaFromDB(cmpPanel);
			
		// Add summary to info table
			new Summary(1, mDB, cmpPanel);
			
    			mDB.executeUpdate("update info set annoState = " + quote("FINISHED"));
    			cmpPanel.allowExecute(true);
	   		PrtDateMsgTime("Complete " + cmpPanel.getDBName() + " at ", startTime);
   	   		cmpPanel.saveCPAVE();
		}
		catch (Exception e) {
			ErrorReport.die("");
		}
	}		

	/***************************************************************
	 * Add new groups
	 ***************************************************************/
	public void addNewMethods () {		
		try {
			if (! openCMPPAVE()) return;
			DBinfo db = new DBinfo(mDB, true); // check version and update
			
			if (! checkProjectPath()) return;
			if (cmpPanel.getBlastPanel().getBlastFile() == null) {
				System.err.println("User Cancelled execution of Add New Cluster");
				return; 
			}
			
			int cnt=0, fail=0;
			boolean rc=true;
			cmpPanel.allowExecute(false);				
			long startTime = getTime();		
			int num = cmpPanel.getMethodCount();
			
			for(int x=0; x<num; x++) {
				String POGtype = cmpPanel.getMethodTypeAt(x);
				if(!cmpPanel.getMethodIsLoaded(x)) {		
					if(POGtype.equals(MethodBestRecipPanel.getMethodType())) {
						rc = new MethodBestRecip().run(x, mDB, cmpPanel);
						if (rc) cnt++; else fail++;
					}					
					else if(POGtype.equals(MethodOrthoMCLPanel.getMethodType())) {
						rc = new MethodOrthoMCL().run(x, mDB, cmpPanel);
						if (rc) cnt++; else fail++;
					}
					else if(POGtype.equals(MethodTransitivePanel.getMethodType())) {
						rc = new MethodTransitive().run(x, mDB, cmpPanel);
						if (rc) cnt++; else fail++;
					}
					else if(POGtype.equals(MethodUserDefPanel.getMethodType())){
						rc = new MethodUserDef().run(x, mDB, cmpPanel);
						if (rc) cnt++; else fail++;
					}
					else {
						System.err.println("Error: cannot load unidentified type '" + POGtype + "'");
					}
				}
			}
			if (cnt>0) new Summary(2, mDB, cmpPanel);
			if (fail > 0) CompileMain.PrtError(" failed methods " + fail);
			PrtDateMsgTime("Complete adding " + cnt + " methods for " + cmpPanel.getDBName() + " at ", startTime);
			
			cmpPanel.getMethodPanel().validateMethods(true);
			cmpPanel.allowExecute(true);
		}
		catch (Exception e) {
			ErrorReport.prtReport(e, "Cannot add Cluster from main");
			return;
		}
	}
	public void addPCC () {
		try {
			long time = getTime();
			PrtDateMsg("Start PCC calculations (this can take a long time for really big clusters) ");
			if (!openCMPPAVE()) {
				PrtError("Cannot open database");
				return;
			}
			Vector <String> prefix = new Vector <String> ();
			ResultSet rs = mDB.executeQuery("SELECT prefix FROM pog_method");
			while (rs.next()) prefix.add(rs.getString(1));
			rs.close();
			for (int i=0; i< prefix.size(); i++) {
				new Pairwise(mDB, prefix.get(i));
			}
			PrtSpMsgTime(0, "Complete PCC for " + prefix.size() + " methods", time);
		}
		catch  (Exception e) {
			ErrorReport.prtReport(e, "Cannot add Cluster from main");
			return;
		}
	}

	/***************************************************************
	 * Database routines
	 ***************************************************************/
	
	// XXX should the database routines are be in compilePanel?
	private boolean validateCmpPAVEdb() {		
		try {
			PrtSpMsg(0, "Checking database");
       		String dbName = cmpPanel.getDBName();
       		PrtSpMsg(1, "Database: " + dbName);
    			
			boolean doesDBExist = DBConn.checkMysqlDB("Validate mTCW ", hosts.host, dbName, hosts.user, hosts.pass);
				
			if (!doesDBExist) 
				return (createCMPPAVE(hosts.host, dbName, hosts.user, hosts.pass));	
			
			if (yesNo("Delete database and restart?")) {
				DBConn.deleteMysqlDB(hosts.host, dbName, hosts.user, hosts.pass);
				return (createCMPPAVE(hosts.host, dbName, hosts.user, hosts.pass));
			}
			// XXX temporary
			else if (yesNo("Update summary (just for development)?")) {
				openCMPPAVE();
				new Summary(0, mDB,  cmpPanel);	
    			cmpPanel.allowExecute(true);
    			return false;
			}
			else die("User abort");
			return true;
		}
		catch (Exception e) {
			ErrorReport.prtReport(e, "Cannot open new multiTCW database");
			return false;
		}
	}
	
	private boolean createCMPPAVE(String url, String dbName, String user, String pass) {
		try {
			DBConn.createMysqlDB(url, dbName, user, pass);
	
			mDB = new DBConn(url, dbName, user, pass);
			//mDB.setAutoCommit(false);
	
			PrtSpMsg(1, "Creating database");
			new Schema(mDB, Globals.VERSION, cmpPanel.getCurrentProjectDirectory());
	
			return true;
		}
		catch (Exception e) {
			ErrorReport.prtReport(e, "Cannot create database");
			return false;
		}
	}
	
	private boolean openCMPPAVE() {
		try {
  			String dbName = cmpPanel.getDBName(); 			
			boolean doesDBExist = hosts.checkDBConnect(dbName);
			if (!doesDBExist) {
				PrtWarn("No database " + dbName);
       			return false;
			}			
			mDB = hosts.getDBConn(dbName);

			return true;
		}
		catch (Exception e) {
			ErrorReport.prtReport(e, "Cannot open compare database");
			return false;
		}
	}

	// get a specific database connection
	static public DBConn getDBConnection(CompilePanel pnl, int assemIndex) throws Exception {
		String dbName = pnl.getAssemblyDB(assemIndex);
		return hosts.getDBConn(dbName);
	}
	
	// get the cmpPAVE connections?
	static public DBConn getDBConnection(CompilePanel pnl) throws Exception {
		String dbName = pnl.getDBName();		
		return hosts.getDBConn(dbName);
	}

	//Allows db connection without assemblyData class
	static public DBConn getDBConnection(String host, String dbName) throws Exception {
		
		return hosts.getDBConn(dbName);
	}
	
	static public boolean doesDBExist(String host, String dbName) {
		try {
			boolean retVal = false;
			
			DBConn conn =  hosts.getDBConn("mysql");
			
			ResultSet rset = conn.executeQuery("SHOW DATABASES");
			while(rset.next() && !retVal) {
				if(dbName.equals(rset.getString(1)))
					retVal = true;
			}
			rset.close();
			conn.close();
			
			return retVal;
		}
		catch(Exception e) {
			ErrorReport.prtReport(e, "Error validating db existing");
		}
		return false;
	}

	/*** May nto be necessary anymore... **/
	
	private boolean checkProjectPath() {
		String projPath = cmpPanel.getCurrentProjectDirectory();
		if (projPath == null) {
			CompileMain.PrtError("No current project " + projPath);
			return false;
		}
			
		File f = new File(projPath);
		if (!f.exists()) {
			CompileMain.PrtError("Cannot find project directory " + projPath);
			return false;
		}
		return true;
	}
	
	private boolean checkAAfiles() {
		try {
			for (int idx=0; idx< cmpPanel.getAssemblyCount(); idx++)
			{
				if (cmpPanel.getSpeciesPanel().isGenerateProteinFileAt(idx)) {
					File ESTScan = new File(Globals.Compile.ESTScanExec);
					if (!ESTScan.exists())
					{
						CompileMain.PrtError("Can't find ESTSCan at " + ESTScan.getAbsolutePath());
						return false;
					}
					File smatFile = new File(cmpPanel.getAssemblySMATFile(idx));
					if (!smatFile.exists())
					{
						CompileMain.PrtError("Can't find smat file " + smatFile.getAbsolutePath());
						return false;
					}
				}
				else {
					String fileName = cmpPanel.getAssemblyProteinFile(idx);				
					if (!fileName.contains(Globals.PROTEINsTCW)) {
						File file =  new File (fileName);
						if (!file.exists()) {
							fileName = cmpPanel.getCurrentProjectDirectory() + "/" + fileName;
	
							file =  new File (fileName);
							if (!file.exists()) {
								CompileMain.PrtError("Missing protein file for " + 
										fileName + " for dataset " + cmpPanel.getAssemblyName(idx));
								return false;
							}
						}
					}
				}
			}
			return true;
		}
		catch (Exception e) {
			ErrorReport.reportError(e,"Problem with AAfiles or SMAT files");
			return false;
		}
	}
	/*****************************************
	 * generateFastaFromDB
	 */
	public static boolean generateFastaFromDB(CompilePanel cmpPanel) {
		try {
			String fastaDir = cmpPanel.getCurrentProjectDirectory() + "/" + Globals.CompilePanel.BLASTDIR;
			String fastaFile = fastaDir + "/" + Globals.CompilePanel.COMBINEDFASTA;  // combined.fasta			
			CompileMain.PrtDateMsg("\nCreate " + fastaFile);
			File testDir = new File(fastaDir);
			if(!testDir.exists()) testDir.mkdir();
			
			DBConn cmpDBC = CompileMain.getDBConnection(cmpPanel);
			int cntDS = cmpDBC.executeCount("SELECT COUNT(*) FROM assembly");
			String [] asmPfx = new String [cntDS+1]; 
			ResultSet rs = cmpDBC.executeQuery("SELECT ASMid, prefix FROM assembly");		
			while(rs.next()) 
				asmPfx[rs.getInt(1)] = rs.getString(2);
			int count=0;
	        BufferedWriter pw = new BufferedWriter(new FileWriter (fastaFile));
	        // we are allowing duplicate sequence identifiers, so need to search by ASMid
	        for (int i=1; i<= cntDS;  i++) {
	        		rs = cmpDBC.executeQuery("SELECT UTstr, aaSeq FROM unitrans" +
	        			" WHERE ASMid=" + i);
				while(rs.next()) {
					String id = rs.getString(1);
					String seq = rs.getString(2);
					if (seq != null) {
						pw.write(">" + asmPfx[i] + "|" + id + "\n");
						pw.write(seq + "\n");
						count++;
					}
				}
	        }
	        CompileMain.PrtSpMsg(1, "Wrote " + count + " sequences");
			rs.close();
			cmpDBC.close();	
			pw.flush();
			pw.close();
			return true;
		}
		catch(Exception e) {
			ErrorReport.prtReport(e, "Error generating fasta from database");
			return false;
		}
	}
	/********** Log File****************/
	public PrintWriter createLogFile() 
	{	   
		String logPath = cmpPanel.getCurrentProjectDirectory() +  "/" + Globals.Compile.logDir;

		// Make sure log directory exists
		File logDir = new File(logPath);
		if (!logDir.exists()) {
			CompileMain.PrtSpMsg(3, "Creating project log directory ...");
			if (!logDir.mkdir()) {
				System.err.println("*** Failed to create project log directory '" + 
						logDir.getAbsolutePath() + "'.");
				return null;
			}
		}
	    	
		// Create log file
		String logFullName = logPath + "/" + Globals.Compile.logFile;
		File logFile = new File (logFullName);
		
		// save last 10 logfiles
		if (logFile.exists()) {
			String logN;
			File fileN;
			boolean success;
			
			for (int i=10; i >= 1; i--) {
				logN = logFullName + "." + Integer.toString(i);
				fileN = new File(logN);
				if (fileN.exists()) {
					logN = logFullName + "." + Integer.toString(i+1);
					File fileNx = new File (logN);
					success = fileN.renameTo(fileNx);
					if (!success)  CompileMain.PrtWarn("could not move " + 
							fileN.getName() + " to " + fileNx.getName());
				}
			}
			logN = logFullName + ".1";
			fileN = new File(logN);
			success = logFile.renameTo(fileN);
			if (!success) CompileMain.PrtWarn("could not move " + logFile.getName()
					+ " to " + fileN.getName());	
		}
		
		try {
			CompileMain.PrtSpMsg(1, "Log file: " + logFile.getAbsolutePath());
			FileOutputStream out = new FileOutputStream(logFile.getAbsolutePath());
			return new PrintWriter(out); 
		}
		catch (Exception e)
        {
            System.err.println ("Error writing to " + logFile.getAbsoluteFile());
            ErrorReport.reportError(e);
            return null;
        }
	}

	 /************************************************************************
     * Methods for printing to logFile, errFile and stdout
     **************************************************************************/
  
	static public void Print(String s) {
	    System.err.println(s);
	    System.out.flush();
	    if (logFileObj != null) { // not open if called by viewPAVE/Version
	    		logFileObj.println(s);
	        logFileObj.flush();
	    }
	}	
	static public void PrtError(String msg)
	{
	    	String s = "\n***Error: " + msg;
	    	Print(s);
	}	
	static public void PrtWarn(String msg)
	{
	    	String s = "+++Warning: " + msg;
	    	Print(s);
	}	
	static public void die(String msg)
	{
   		String s = "***Abort execution: " + msg;
		Print(s);
		System.exit(-1);
	}	
	static public long getTime () {
	    return System.currentTimeMillis(); 
	}	
	static public void PrtDateMsg (String msg) {
	     Print(msg + " " + TimeHelpers.getDate());
	}		
    static public void PrtDateMsgTime (String msg, long t)
    {
        Print(msg + " " + TimeHelpers.getDate() + " Elapse time " + 
    					TimeHelpers.getElapsedTimeStr(t));
    }	
    static public void PrtSpMsgTime (int i, String msg, long t)
    {
    		String sp = "";
    		for (int j=0; j < i; j++) sp += "   ";
    		String x = String.format("%s%-40s   %s", sp, msg, 
    					TimeHelpers.getElapsedTimeStr(t));
        Print(x);
    } 
    
    static public void PrtSpMsgTimeMem (int i, String msg, long t)
    {
    		String sp = "";
    		for (int j=0; j < i; j++) sp += "   ";
    		long mem = Runtime.getRuntime().totalMemory() -
  			      Runtime.getRuntime().freeMemory();
    		int m = (int) (((double) mem / 1000000.0)+0.5);
       	String x = String.format("%s%-40s   %8s  (%dM)", sp, msg, 
       			TimeHelpers.getElapsedTimeStr(t),m);
        Print(x);
    } 
    static public void PrtSpMsg (int i, String msg) {
    		String sp = "";
    		for (int j=0; j < i; j++) sp += "   ";
        sp += msg;
        Print(sp);
    }
	
	public static boolean yesNo(String question)
	{
		if (noPrompt) return true;
		BufferedReader inLine = new BufferedReader(new InputStreamReader(System.in));

		System.out.print("?--" + question + " (y/n)? "); 
		try {
			String resp = inLine.readLine();
			if (resp.equalsIgnoreCase("y")) return true;
			return false;
		} catch (Exception e) {
			return false;
		}
	}
	
	public static boolean overWrite(String filename) {
		try {
			File f = new File(filename);
			if (f.exists()) {
				return yesNo("File " + filename + " exists. OverWrite?");
			}
			return true;
		}
		catch (Exception e) {
			ErrorReport.reportError("Error checking file " + filename, e);
			return true;
		}
	}
	private String quote(String word) {
		return "\"" + word + "\""; 
	}
	public static boolean checkGODB() {
		boolean ret = false;
		if (!strGODB.equals(""))
		{
			try
			{
				ResultSet rs = mDB.executeQueryNoCatch("describe  " + strGODB + ".PAVE_gotree" );
				if (rs.first())
				{
					ret = true;
				}
			}
			catch (Exception e) {
			}
		}
		return ret;
	}
	public static void checkAddGoColumns() throws Exception
	{
		if (!mDB.tableHasColumn("unique_hits", "go_list"))
		{
			PrtSpMsg(1,"Adding GO columns...");
			mDB.executeUpdate("alter table unique_hits add go_list mediumtext after sequence");
		}
		if (!mDB.tableHasColumn("pog_groups", "go_list"))
		{
			mDB.executeUpdate("alter table pog_groups add go_list mediumtext after taxa");
		}
	}
	/**************************************************************
	 *  private
	 ************************************************************/
	static private boolean noPrompt;
	static public PrintWriter logFileObj;
	
	ViewerFrame theFrame = null;
	CompilePanel cmpPanel = null;
	static DBConn mDB = null;
	public static HostsCfg hosts;
}